/*
 * 文件名： DataEncoder.java
 * 
 * 创建日期： 2016年12月13日
 *
 * Copyright(C) 2016, by <a href="mailto:liws@xingyuanauto.com">liws</a>.
 *
 * 原始作者: liws
 *
 */
package com.moli.iov.protocol.codec;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.moli.iov.protocol.enums.DateTimeTag;
import com.moli.iov.protocol.enums.IDictsEnumTag;
import com.moli.iov.protocol.enums.IDictsMultiEnumTag;
import com.moli.iov.protocol.util.BccUtils;
import com.moli.iov.util.ByteArrayUtil;
import com.moli.iov.util.DateUtil;
import com.moli.iov.util.NumberUtil;


/**
 * 协议编码器
 *
 * @author <a href="mailto:liws@xingyuanauto.com">liws</a>
 *
 * @version $Revision$
 * @param <T>
 *
 * @since 2016年12月13日
 */
public class DataEncoder<T> {

	// 缓存对象字段信息
	private static final Map<Class<?>, List<Field>> fieldsCache = new HashMap<Class<?>, List<Field>>();

	// 缓存对象get方法信息
	private static final Map<Class<?>, List<Method>> getMethodsCache = new HashMap<Class<?>, List<Method>>();

	// 缓存Class 与 注解对象 对应关系
	private static final Map<Class<?>, Serialize> classesSerializeCache = new HashMap<Class<?>, Serialize>();

	// 缓存method 与 注解对象 对应关系
	private static final Map<Method, Serialize> methodsSerializeCache = new HashMap<Method, Serialize>();

	// 缓存field 与 注解对象 对应关系
	private static final Map<Field, Serialize> fieldssSerializeCache = new HashMap<Field, Serialize>();

	private T instance;

	private boolean isBigEndian = true;

	private int bytesLen = 0;

	private byte[] bytes;

	private boolean containSuper = true;

	public DataEncoder() {
	}

	public DataEncoder<T> containSuper(boolean value) {
		this.containSuper = value;
		return this;
	}

	/**
	 * 由对象转为byte数组
	 */
	public byte[] encode(T instance) throws Exception {
		byte[] data = encode1(instance);
		// 获取数据单元长度 并设置
		short dataLen = (short) (data.length - 24);
		byte[] lenBytes = ByteArrayUtil.toBytes(dataLen);
		data[22] = lenBytes[0];
		data[23] = lenBytes[1];

		// 协议末尾添加BCC校验位
		data = BccUtils.bccBodyEncode(data);
		return data;
	}
	
	public byte[] encode1(T instance) throws Exception {
		if (instance == null)
			return null;
		this.bytesLen = 0;
		this.instance = instance;
		Serialize ser = null;
		Class<?> objClass = instance.getClass();
		if ((ser = classesSerializeCache.get(objClass)) == null) {
			if (objClass.isAnnotationPresent(Serialize.class)) {
				ser = (Serialize) objClass.getAnnotation(Serialize.class);
				classesSerializeCache.put(objClass, ser);
			}
			else {
				return null;
			}
		}
		isBigEndian = ser.isBigEndian();
		bytes = new byte[(int) Math.pow(256, 2)];
		// 字段信息
		List<Field> fields = null;
		if ((fields = fieldsCache.get(objClass)) == null) {
			if (containSuper)
				fields = Arrays.asList(objClass.getFields());
			else
				fields = Arrays.asList(objClass.getDeclaredFields());
			// 对field进行过滤操作
			List<Field> filterFields = new ArrayList<Field>();
			for (Field field : fields) {
				if (field.isAnnotationPresent(Serialize.class)) {
					field.setAccessible(true);
					filterFields.add(field);
				}
			}
			// 对注解字段进行排序 主要解决 协议对象中 存在变长字节序的情况
			if (ser.isSort()) {
				Collections.sort(filterFields, new Comparator<Field>() {
					@Override
					public int compare(Field o1, Field o2) {
						Serialize s1 = fieldssSerializeCache.get(o1);
						if (s1 == null) {
							s1 = o1.getAnnotation(Serialize.class);
							fieldssSerializeCache.put(o1, o1.getAnnotation(Serialize.class));
						}
						Serialize s2 = fieldssSerializeCache.get(o2);
						if (s2 == null) {
							s2 = o2.getAnnotation(Serialize.class);
							fieldssSerializeCache.put(o2, s2);
						}
						return s1.sort() - s2.sort();
					}
				});
			}
			fields = filterFields;
			fieldsCache.put(objClass, filterFields);
		}
		for (Field field : fields) {
			fillBytes(field);
		}
		// 方法信息
		List<Method> methods = null;
		if ((methods = getMethodsCache.get(objClass)) == null) {
			if (containSuper)
				methods = Arrays.asList(objClass.getMethods());
			else
				methods = Arrays.asList(objClass.getDeclaredMethods());
			// 对field进行过滤操作
			List<Method> filterMethods = new ArrayList<Method>();
			for (Method method : methods) {
				if (isGetter(method) && method.isAnnotationPresent(Serialize.class)) {
					method.setAccessible(true);
					filterMethods.add(method);
				}
			}
			methods = filterMethods;
			getMethodsCache.put(objClass, filterMethods);
		}
		for (Method method : methods) {
			fillBytes(method);
		}

		byte[] data = new byte[bytesLen];
		data = new byte[bytesLen];
		System.arraycopy(bytes, 0, data, 0, bytesLen);
		return data;
	}

	private Object getFieldValue(Field field) throws IllegalAccessException, IllegalArgumentException {
		Class<?> clazz = field.getType();
		if (Void.class.isAssignableFrom(clazz) || void.class.isAssignableFrom(clazz))
			return null;
		if (Byte.class.isAssignableFrom(clazz) || byte.class.isAssignableFrom(clazz)) {
			return field.getByte(instance);
		}
		else if (Short.class.isAssignableFrom(clazz) || short.class.isAssignableFrom(clazz)) {
			return field.getShort(instance);
		}
		else if (Integer.class.isAssignableFrom(clazz) || int.class.isAssignableFrom(clazz)) {
			return field.getInt(instance);
		}
		else if (Long.class.isAssignableFrom(clazz) || long.class.isAssignableFrom(clazz)) {
			return field.getLong(instance);
		}
		else if (Float.class.isAssignableFrom(clazz) || float.class.isAssignableFrom(clazz)) {
			return field.getFloat(instance);
		}
		else if (Double.class.isAssignableFrom(clazz) || double.class.isAssignableFrom(clazz)) {
			return field.getDouble(instance);
		} // 单字节 枚举类型
		else if (IDictsEnumTag.class.isAssignableFrom(clazz)) {
			IDictsEnumTag obj = (IDictsEnumTag) field.get(instance);
			if (obj == null)
				return null;
			return obj.getV();
		} // 字节数组 枚举类型
		else if (IDictsMultiEnumTag.class.isAssignableFrom(clazz)) {
			IDictsMultiEnumTag multiObj = (IDictsMultiEnumTag) field.get(instance);
			if (multiObj == null)
				return null;
			return multiObj.getV();
		}
		else {
			return field.get(instance);
		}
	}

	private byte[] toBytes(Object value, Serialize si) throws UnsupportedEncodingException {
		byte[] data = null;
		int precision = si.precision();
		if(precision==0) precision = 2;
		if (value instanceof Byte) {
			data = new byte[1];
			byte result = (byte) ((NumberUtil.toByte(value) + (byte) si.valOffset()) / (byte) si.coefficient());
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			data[0] = result;
		}
		else if (value instanceof Byte[] || value instanceof byte[]) {
			data = (byte[]) value;
		}
		else if (value instanceof Short) {
			short result = (short) ((NumberUtil.toShort(value) + (short) si.valOffset()) / si.coefficient());
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) result);
			else
				data = ByteArrayUtil.toLittleBytes((long) result);
		}
		else if (value instanceof Integer) {
			int val = NumberUtil.toInteger(value);
			int result = (int) ((val + (int) si.valOffset()) / si.coefficient());
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) result);
			else
				data = ByteArrayUtil.toLittleBytes((long) result);
		}
		else if (value instanceof Long) {
			long result = (long) ((NumberUtil.toLong(value) + (long) si.valOffset()) / si.coefficient());
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) result);
			else
				data = ByteArrayUtil.toLittleBytes((long) result);
		}
		else if (value instanceof Float) {
			double result = (NumberUtil.toFloat(value) + si.valOffset()) / si.coefficient();
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) result);
			else
				data = ByteArrayUtil.toLittleBytes((long) result);
		}
		else if (value instanceof Double) {
			double result = (NumberUtil.toDouble(value) + si.valOffset()) / si.coefficient();
			BigDecimal bg = new BigDecimal(result);
			result = bg.setScale(precision, BigDecimal.ROUND_HALF_UP).doubleValue();
			if (result > si.max() || result < si.min()) {
				data = new byte[si.size()];
				Arrays.fill(data, (byte) 0xFF);
				return data;
			}
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) result);
			else
				data = ByteArrayUtil.toLittleBytes((long) result);
		}
		else if (value instanceof String) {
			byte[] asciiData = ByteArrayUtil.strToAscii((String) value);
			int dataLen = asciiData.length;
			int realLen = si.size();
			if (dataLen < realLen) {
				data = Arrays.copyOf(asciiData, realLen);
			}
			else {
				data = asciiData;
			}
		}
		else if (value instanceof Date) {
			Date date = (Date) value;
			int ts = DateUtil.toUnixTimestamp(date);
			if (isBigEndian)
				data = ByteArrayUtil.toBytes((long) ts);
			else
				data = ByteArrayUtil.toLittleBytes((long) ts);
		}
		else if (value instanceof DateTimeTag) {
			data = ((DateTimeTag) value).toBytes();
		}
		return data;
	}

	private void fillBytes(Method method) throws UnsupportedEncodingException, IllegalAccessException,
			IllegalArgumentException, InvocationTargetException {
		Serialize si = null;
		if ((si = methodsSerializeCache.get(method)) == null) {
			si = method.getAnnotation(Serialize.class);
			methodsSerializeCache.put(method, si);
		}
		Object value = method.invoke(instance);
		byte[] data = null;
		// 空值，写入空数组
		if (value == null && si.size() > 0)
			data = new byte[si.size()];
		else if (value == null)// 空值，并且长度小于等于0，不写入
			return;
		else// 获取值取出对应的byte数组
			data = toBytes(value, si);

		if (data == null) {
			return;
		}
		int len = si.size() == -1 ? data.length : si.size();
		if (isBigEndian)
			System.arraycopy(data, data.length - len, bytes, si.offset(), len);
		else
			System.arraycopy(data, 0, bytes, si.offset(), len);
		if (bytesLen < si.offset() + len)
			bytesLen = si.offset() + len;
	}

	private void fillBytes(Field field)
			throws IllegalArgumentException, IllegalAccessException, UnsupportedEncodingException {
		Serialize si = null;
		if ((si = fieldssSerializeCache.get(field)) == null) {
			si = (Serialize) field.getAnnotation(Serialize.class);
			fieldssSerializeCache.put(field, si);
		}
		Object value = getFieldValue(field);
		byte[] data = null;
		// 空值，写入空数组
		if (value == null && si.size() > 0) {
			data = new byte[si.size()];
			for (int i = 0; i < si.size(); i++) {// 空值表示此值为无效数据
				data[i] = (byte) 0xFF;
			}
		}
		else if (value == null)// 空值，并且长度小于等于0，不写入
			return;
		else// 获取值取出对应的byte数组
			data = toBytes(value, si);

		if (data == null) {
			return;
		}
		// 按照sort 和 size进行编码排序
		if (si.sort() != -1) {
			// 获取字段注解size 字节序size
			int size = si.size();
			// 判断size是否 为实际数据size大小 若为-1表示 长度为实际数据长度,否则以配置size值为准
			int len = size == -1 ? data.length : size;
			// 校验数据长度是否超过配置size长度,若超过以配置为准
			len = data.length >= len ? len : data.length;
			// 设置偏移量为 数据实际数据长度最大值(追加方式)
			int offset = bytesLen;
			if (isBigEndian) {
				// int tmp_offset = si.size() == -1 ? si.offset() : si.offset()
				// + (size - len);
				System.arraycopy(data, data.length - len, bytes, offset, len);
			}
			else {
				// offset = si.offset();
				System.arraycopy(data, 0, bytes, offset, len);
			}
			len = si.size() == -1 ? len : si.size();
			if (bytesLen < offset + len)
				bytesLen = offset + len;
		}
		else {// 按照原有offset 和 size方式编码
			int size = si.size();
			int len = size == -1 ? data.length : size;
			len = data.length >= len ? len : data.length;
			int offset = si.offset();
			if (isBigEndian) {
				int tmp_offset = si.size() == -1 ? si.offset() : si.offset() + (size - len);
				System.arraycopy(data, data.length - len, bytes, tmp_offset, len);
			}
			else {
				offset = si.offset();
				System.arraycopy(data, 0, bytes, offset, len);
			}
			len = si.size() == -1 ? len : si.size();
			if (bytesLen < offset + len)
				bytesLen = offset + len;
		}
	}

	private static boolean isGetter(Method method) {
		if (!method.getName().startsWith("get"))
			return false;
		if (method.getParameterTypes().length != 0)
			return false;
		if (void.class.equals(method.getReturnType()))
			return false;
		return true;
	}
}
